/********************************************************************************
*                 Copyright (C) 2011 - Las Venturas Playground                  *
*                      http://sa-mp.nl | http://lvp-iv.com                      *
*                                                                               *
*    This program is free software: you can redistribute it and/or modify       *
*    it under the terms of the GNU General Public License as published by       *
*    the Free Software Foundation, either version 3 of the License, or          *
*    (at your option) any later version.                                        *
*                                                                               *
*    This program is distributed in the hope that it will be useful,            *
*    but WITHOUT ANY WARRANTY; without even the implied warranty of             *
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the              *
*    GNU General Public License for more details.                               *
*                                                                               *
*    You should have received a copy of the GNU General Public License          *
*    along with this program.  If not, see <http://www.gnu.org/licenses/>.      *
*                                                                               *
*********************************************************************************
*                                                                               *
*  Las Venturas Playground - Minigames/minigameCore.nut                         *
*                                                                               *
*  The minigame core. This file is in charge of initiating minigame instances,  *
*  as well as handling the signups and signouts of players. It also catches     *
*  events that might be of interest to the various minigame handlers, and       *
*  passes them on to the currently running minigames.                           *
*  I initially went with an object-oriented approach, but later I decided to    *
*  switch to a procedural design in order to facilitate the callback handling,  *
*  and because there will be only 1 minigame core anyway.                       *
*                                                                               *
*   Author:                                                                     *
*   Pugwipe <pugwipe@gmail.com>                                                 *
*                                                                               *
********************************************************************************/

Minigames <- 
{
    Definition = {},
    Instance = {},
    
    Core = {}
};

/* 
 * Function: Minigames::Core::initiateMinigame ()
 * This function initiates (that is, starts the signup period) for a minigame.
 *
 * @param minigameDefinition  A minigame definition instance that inherits from Minigames.Definition.Base.
 * @param instanceType  A class definition (NOT an instance) that inherits from Minigames.Instance.Base.
 */ 
function Minigames::Core::initiateMinigame (minigameDefinition, instanceType)
{
    local minigame = instanceType (minigameDefinition);
    this.activeMinigames.append (minigame);
    sendMessageToAll (minigameDefinition.formatStartMessage ());
    return minigame;
}

/* 
 * Function: Minigames::Core::endMinigame ()
 * This function ends a minigame. This MUST be requested by the minigame's own handler.
 * The core will then do all the necessary clean up operations, and if the garbage collector
 * does its job, the minigame instance won't exist anymore after this function ends.
 * 
 * @param minigameInstance  A minigame instance that inherits from Minigames.Instance.Base.
 */
function Minigames::Core::endMinigame (minigameInstance, reason)
{
    local len = this.activeMinigames.len ();        
    for (local i = len-1; i >= 0; i -= 1)  // loop in reverse to deal with the removal of items
    {
        if (this.activeMinigames[i] == minigameInstance) 
        {
            local players = minigameInstance.getPlayersInMinigame ();
            
            foreach (playerid in players)
            {
                if (playerid in this.minigamePlayers) delete this.minigamePlayers [playerid];
            }
            
            this.activeMinigames.remove (i);
        }
    }
    
    if (reason == Minigames.EndReason.NotEnoughSignups) 
    {
        local minigameName = minigameInstance.getDefinition ().getMinigameName ();
    }
}

/* 
 * Function: Minigames::Core::registerMinigame ()
 * This function will bind a minigame definition to a command.
 *
 * @param minigameDefinition  Definition instance, inherited from Minigames.Definition.Base.
 * @param instanceType  Class definition, inherited from Minigames.Instance.Base.
 */
function Minigames::Core::registerMinigame (minigameDefinition, instanceType)
{   
    local command = minigameDefinition.getCommand ();
    if (!command) return;
    
    command = command.tolower ();
    this.cmdHandler.registerCommand (command, Command (this.onCommand), COMMAND_LEVEL_PLAYER);
    this.loadedMinigames[command] <- [minigameDefinition, instanceType];
}

/*
 * Function: Minigames::Core::signPlayerUpForMinigame ()
 * This function signs a player up for a minigame instance. Note that the minigame core 
 * is responsible for this, and therefore you should always call this function if you
 * want to sign someone up, rather than Instance::Base::signPlayerUp.
 * 
 * @param playerid  The playerid
 * @param minigameInstance  Minigame instance, inherited from Minigames.Instance.Base.
 */
function Minigames::Core::signPlayerUpForMinigame (playerid, minigameInstance)
{
    if (minigameInstance.signPlayerUp (playerid))
    {
        this.minigamePlayers[playerid] <- minigameInstance;
    }    
}

/*
 * Function: Minigames::Core::signPlayerOutFromMinigame ()
 * This function signs a player out from a minigame. Note that the minigame core
 * is responsible for this, and therefore you should always call this function if you
 * want to sign someone out, rather than Instance::Base::signPlayerOut.
 *
 * @param playerid  The player ID
 */
function Minigames::Core::signPlayerOutFromMinigame (playerid)
{
    if (!(playerid in this.minigamePlayers)) return;
    
    local minigameInstance = this.minigamePlayers[playerid];
    minigameInstance.endForPlayer (playerid);
}
    
/*
 * Function: Minigames::Core::getPlayerMinigame ()
 * This function gets the minigame the player is currently playing, if any.
 *
 * @param playerid  The player ID
 * 
 * @return  The minigame instance the player is currently playing in.
 */
function Minigames::Core::getPlayerMinigame (playerid)
{
    if (playerid in this.minigamePlayers) return this.minigamePlayers[playerid];
     
    return null;
}

/*
 * Function:  Minigames::Core::onCommand ()
 * This callback function is called when a player types a command that was registered
 * as a minigame command. It will then perform the necessary action - that is, start a new minigame,
 * sign a player up for an existing one, or do nothing if the minigame can't be started.
 *
 * @param playerid  The player who typed the command
 * @param commandParams  An array containing the command parameters
 * @param commandText  The whole command string the player typed
 */
function Minigames::Core::onCommand (playerid, commandParams, commandText)
{
    local trigger = commandParams[0].tolower ();
    if (!(trigger in Minigames.Core.loadedMinigames)) return;

    local definition = Minigames.Core.loadedMinigames[trigger][0];
    local instanceType = Minigames.Core.loadedMinigames[trigger][1];
        
    local count = 0;
    local currentlyActive = null;
        
    // Loop through all minigame instances to see if there's one that matches the definition
    // AND currently allows for signups
    foreach (minigame in Minigames.Core.activeMinigames) 
    {
        if (definition == minigame.getDefinition ()) 
        {
            if (minigame.getState () == Minigames.State.Registering) currentlyActive = minigame;
            count++;
        }
    }
        
    if (currentlyActive) 
    {
        // There's currently a minigame of this type going on, AND the player is allowed to
        // sign up for it. Let's do it.
        Minigames.Core.signPlayerUpForMinigame (playerid, currentlyActive);
    } 
        
    else if (count >= definition.getMaxInstances ()) 
    {
	    // This minigame cannot be started at this time
		sendPlayerErrorMessage (playerid, "* Too many instances of this are already running.");
    }
        
    else 
    {
        // This minigame can and has to be started here and now
        local minigame = Minigames.Core.initiateMinigame (definition, instanceType);
        Minigames.Core.signPlayerUpForMinigame (playerid, minigame);
     }       
}

/*
 * Function: Minigames::Core::onUpdate ()
 * This callback function will be called once every second, to give the various minigame handlers
 * a chance to process things that can't be handled in events.
 */
function Minigames::Core::onUpdate ()
{
    foreach (minigame in Minigames.Core.activeMinigames)
    {
        minigame.update ();
    }
}

/*
 * Function: Minigames::Core::onPlayerWasted ()
 * This callback function is called when a player dies. It will check if the player is in a minigame,
 * and if so, pass the event on to said minigame.
 *
 * @param playerid  The player that got killed
 * @param killerid  The player who killed playerid
 */
function Minigames::Core::onPlayerWasted (playerid, killerid, killervehicle)
{
    local minigame = Minigames.Core.getPlayerMinigame (playerid);
    if (minigame) minigame.onPlayerWasted (playerid, killerid, killervehicle);
}

addEvent ("playerDeath", Minigames.Core.onPlayerWasted);

/*
 * Function: Minigames::Core::onPlayerConnect ()
 * This callback function is called when a player connects. It will check if the player is in a minigame,
 * and if so, pass the event on to said minigame. I don't know what the use of this is, but oh well.
 *
 * @param playerid  The player who connected
 */
function Minigames::Core::onPlayerConnect (playerid)
{
    local minigame = Minigames.Core.getPlayerMinigame (playerid);
    if (minigame) minigame.onPlayerConnect (playerid);
}

addEvent ("playerConnect", Minigames.Core.onPlayerConnect);

/*
 * Function: Minigames::Core::onPlayerDisconnect ()
 * This callback function is called when a player disconnects. It will check if the player is in a minigame,
 * and if so, pass the event on to said minigame.
 *
 * @param playerid  The player who disconnected
 */
function Minigames::Core::onPlayerDisconnect (playerid, reason)
{
    local minigame = Minigames.Core.getPlayerMinigame (playerid);
    if (minigame) minigame.onPlayerDisconnect (playerid, reason);
}

addEvent ("playerDisconnect", Minigames.Core.onPlayerDisconnect);

/*
 * Function: Minigames::Core::onPlayerDisconnect ()
 * This callback function is called when a player's state changes. That is, when a vehicle
 * is entered or exited.
 *
 * @param playerid  The player who disconnected
 * @param oldstate  The old state
 * @param newstate  The new state
 */ 
function Minigames::Core::onPlayerStateChange (playerid, oldstate, newstate)
{
    local minigame = Minigames.Core.getPlayerMinigame (playerid);
    if (minigame) minigame.onPlayerStateChange (playerid, oldstate, newstate);
}

addEvent ("playerStateChange", Minigames.Core.onPlayerStateChange);

/*
 * Function: Minigames::Core::onInit ()
 * This callback function is called when the script is started.
 * It simply initializes the minigame handler.
 */
function Minigames::Core::onInit ()
{
    Minigames.Core.minigamePlayers <- {}; // Table containing the players that are currently playing a minigame
    Minigames.Core.activeMinigames <- []; // The currently active minigames
    
    Minigames.Core.cmdHandler <- CommandHandler (); // Command handler object associated with the minigame core
    Minigames.Core.loadedMinigames <- {}; // Table containing all registered minigame definitions
    
    local updateTimer = timer (Minigames.Core.onUpdate, 1000, -1);
}

addEvent ("scriptInit", Minigames.Core.onInit);